// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/events/gestures/fling_curve.h"

#include <algorithm>
#include <cmath>

#include "base/logging.h"

namespace {

const float kDefaultAlpha = -5.70762e+03f;
const float kDefaultBeta = 1.72e+02f;
const float kDefaultGamma = 3.7e+00f;

inline double GetPositionAtTime(double t)
{
    return kDefaultAlpha * exp(-kDefaultGamma * t) - kDefaultBeta * t - kDefaultAlpha;
}

inline double GetVelocityAtTime(double t)
{
    return -kDefaultAlpha * kDefaultGamma * exp(-kDefaultGamma * t) - kDefaultBeta;
}

inline double GetTimeAtVelocity(double v)
{
    return -log((v + kDefaultBeta) / (-kDefaultAlpha * kDefaultGamma)) / kDefaultGamma;
}

} // namespace

namespace ui {

FlingCurve::FlingCurve(const gfx::Vector2dF& velocity,
    base::TimeTicks start_timestamp)
    : curve_duration_(GetTimeAtVelocity(0))
    , start_timestamp_(start_timestamp)
    , previous_timestamp_(start_timestamp_)
    , time_offset_(0)
    , position_offset_(0)
{
    DCHECK(!velocity.IsZero());
    float max_start_velocity = std::max(fabs(velocity.x()), fabs(velocity.y()));
    if (max_start_velocity > GetVelocityAtTime(0))
        max_start_velocity = GetVelocityAtTime(0);
    CHECK_GT(max_start_velocity, 0);

    displacement_ratio_ = gfx::Vector2dF(velocity.x() / max_start_velocity,
        velocity.y() / max_start_velocity);
    time_offset_ = GetTimeAtVelocity(max_start_velocity);
    position_offset_ = GetPositionAtTime(time_offset_);
}

FlingCurve::~FlingCurve()
{
}

bool FlingCurve::ComputeScrollOffset(base::TimeTicks time,
    gfx::Vector2dF* offset,
    gfx::Vector2dF* velocity)
{
    DCHECK(offset);
    DCHECK(velocity);
    base::TimeDelta elapsed_time = time - start_timestamp_;
    if (elapsed_time < base::TimeDelta()) {
        *offset = gfx::Vector2dF();
        *velocity = gfx::Vector2dF();
        return true;
    }

    bool still_active = true;
    float scalar_offset;
    float scalar_velocity;
    double offset_time = elapsed_time.InSecondsF() + time_offset_;
    if (offset_time < curve_duration_) {
        scalar_offset = GetPositionAtTime(offset_time) - position_offset_;
        scalar_velocity = GetVelocityAtTime(offset_time);
    } else {
        scalar_offset = GetPositionAtTime(curve_duration_) - position_offset_;
        scalar_velocity = 0;
        still_active = false;
    }

    *offset = gfx::ScaleVector2d(displacement_ratio_, scalar_offset);
    *velocity = gfx::ScaleVector2d(displacement_ratio_, scalar_velocity);
    return still_active;
}

bool FlingCurve::ComputeScrollDeltaAtTime(base::TimeTicks current,
    gfx::Vector2dF* delta)
{
    DCHECK(delta);
    if (current <= previous_timestamp_) {
        *delta = gfx::Vector2dF();
        return true;
    }

    previous_timestamp_ = current;

    gfx::Vector2dF offset, velocity;
    bool still_active = ComputeScrollOffset(current, &offset, &velocity);

    *delta = offset - cumulative_scroll_;
    cumulative_scroll_ = offset;
    return still_active;
}

} // namespace ui
